// read-write_lock.cpp


#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <ctime>

#include <chrono>
#include <condition_variable>
#include <mutex>
#include <string>
#include <thread>


namespace blogs
{


using std::chrono::milliseconds;
using std::condition_variable;
using std::lock_guard;
using std::mutex;
using std::string;
using std::unique_lock;


class Read_Write_Lock
{

  private:

    int
    __readingreaders__;


    int
    __waitingwriters__;


    int
    __writingwriters__;


    bool
    __preferwriter__;


    mutex
    __mtx__;


    condition_variable
    __cv__;


  public:

    Read_Write_Lock()
        : __readingreaders__(0), __waitingwriters__(0), __writingwriters__(0),
          __preferwriter__(true)
    {
    }


    void
    read_lock()
    {
        unique_lock<mutex> lk(__mtx__);
        __cv__.wait(lk,
                    [this] {
                        bool write = __writingwriters__ > 0 ||
                                    (__preferwriter__ && __waitingwriters__ > 0);
                        if (write)
                            std::printf("Somebody is writing or to write. Waiting to read...\n");
                        return !write;
                    });
        ++__readingreaders__;
    }


    void
    read_unlock()
    {
        lock_guard<mutex> lk(__mtx__);
        --__readingreaders__;
        __preferwriter__ = true;
        __cv__.notify_all();
    }


    void
    write_lock()
    {
        unique_lock<mutex> lk(__mtx__);
        ++__waitingwriters__;
        __cv__.wait(lk,
                    [this] {
                        bool nowrite = __readingreaders__ > 0 || __writingwriters__ > 0;
                        if (nowrite)
                            std::printf("Somebody is reading or writing. Waiting to write...\n");
                        return !nowrite;
                    });
        --__waitingwriters__;
        ++__writingwriters__;
    }


    void
    write_unlock()
    {
        lock_guard<mutex> lk(__mtx__);
        --__writingwriters__;
        __preferwriter__ = false;
        __cv__.notify_all();
    }

};


class Data
{

  private:

    int
    __capacity__;


    string
    __buffer__;


    Read_Write_Lock
    __lock__;


    string
    __do_read__()
    {
        return __buffer__;
    }


    void
    __do_write__(char c)
    {
        for (int i = 0; i < __capacity__; ++i) {
            __buffer__[i] = c;
            std::this_thread::sleep_for(milliseconds(50));
        }
    }


  public:

    Data(int cap = 10)
        : __capacity__(cap), __buffer__(cap, '*'), __lock__()
    {
    }


    string
    read()
    {
        __lock__.read_lock();
        string content = __do_read__();
        __lock__.read_unlock();
        return content;
    }


    void
    write(char c)
    {
        __lock__.write_lock();
        __do_write__(c);
        __lock__.write_unlock();
    }

};


class Reader
{

  private:

    string
    __name__;


    Data &
    __data__;


  public:

    Reader(string nm, Data & data)
        : __name__(nm), __data__(data)
    {
    }


    void
    run()
    {
        while (true) {
            string content = __data__.read();
            std::printf("%s read %s\n", __name__.c_str(), content.c_str());
            std::this_thread::sleep_for(milliseconds(std::rand() % 700));
        }
    }

};


class Writer
{

  private:

    string
    __name__;


    Data &
    __data__;


    string
    __filler__;


    int
    __index__;


    char
    __nextchar__()
    {
        char c = __filler__[__index__];
        ++__index__;
        if (__index__ >= __filler__.size())
            __index__ = 0;
        return c;
    }


  public:

    Writer(string nm, Data & data, string fle)
        : __name__(nm), __data__(data), __filler__(fle), __index__(0)
    {
    }


    void
    run()
    {
        while (true) {
            char c = __nextchar__();
            __data__.write(c);
            std::printf("%s wrote %c\n", __name__.c_str(), c);
            std::this_thread::sleep_for(milliseconds(std::rand() % 300));
        }
    }

};


void
bye(int sig = SIGTERM)
{
    std::printf("\nBye Read-Write Lock...\n\n");
    std::exit(sig);
}


} // end of namespace blogs


int
main(int argc, char * argv[])
{
    using std::thread;
    using blogs::Data;
    using blogs::Read_Write_Lock;
    using blogs::Reader;
    using blogs::Writer;

    std::signal(SIGINT, blogs::bye);
    std::srand(std::time(0));

    Data data(26);

    Writer w1("Writer-1", data, "abcdefghijklmnopqrstuvwxyz");
    Writer w2("Writer-2", data, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
    Reader r1("Reader-1", data);
    Reader r2("Reader-2", data);
    Reader r3("Reader-3", data);
    Reader r4("Reader-4", data);
    Reader r5("Reader-5", data);
    Reader r6("Reader-6", data);

    thread wt1(&Writer::run, &w1);
    thread wt2(&Writer::run, &w2);
    thread rt1(&Reader::run, &r1);
    thread rt2(&Reader::run, &r2);
    thread rt3(&Reader::run, &r3);
    thread rt4(&Reader::run, &r4);
    thread rt5(&Reader::run, &r5);
    thread rt6(&Reader::run, &r6);

    wt1.join();
    wt2.join();
    rt1.join();
    rt2.join();
    rt3.join();
    rt4.join();
    rt5.join();
    rt6.join();

    blogs::bye();
    return 0;
}

